#ifndef A8_OPENSSL_HPP
#define A8_OPENSSL_HPP

#include <string>
#include <string.h>
#include <openssl/md5.h>  
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/pem.h>
#include <a8/strutils.hpp>

namespace a8
{
	namespace openssl
	{
		static std::string md5(std::string msg)  
		{  
			const int digestLen = 16;  
			unsigned char digest[digestLen] = {0};  
			memset(digest, 0x00, sizeof(digest));  
  
			MD5_CTX ctx;  
			MD5_Init(&ctx);  
			MD5_Update(&ctx, msg.c_str(), msg.size());  
			MD5_Final(digest, &ctx);  

			char buf[33] = {0};
			for(int i = 0; i< digestLen; i++ ){
				char tmp[3] = {0};
				sprintf(tmp, "%02X", digest[i]);
				strcat(buf, tmp);
			}  
			for(int i = 0; i < 33; i++){
				if(buf[i] >= 'A' && buf[i] <= 'Z'){
					buf[i] += 32;
				}
			}
			return std::string(buf);  
		}  

		static RSA* CreatePublicRSA(unsigned char* key)
		{
			BIO *bio = BIO_new_mem_buf(key, -1);  
			if(bio == NULL){  
				return NULL;  
			}
			RSA *rsa = NULL;  
			//-----BEGIN RSA PUBLIC KEY-----
			//rsa = PEM_read_bio_RSAPublicKey(bio, &rsa, NULL, NULL);  
			//-----BEGIN PUBLIC KEY-----
			rsa = PEM_read_bio_RSA_PUBKEY(bio, &rsa, NULL, NULL);  
			return rsa;
		}

		static RSA* CreatePrivateRSA(unsigned char* key)
		{
			RSA *rsa = NULL;  
			BIO *keybio = NULL;  
			keybio = BIO_new_mem_buf(key, -1);  
			if(keybio == NULL){  
				return NULL;  
			}
			rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);  
			return rsa;
		}

		static bool RSAPublicDecrypt(std::string& from, std::string& to, RSA *rsa, int padding)
		{
			int rsa_len = RSA_size(rsa);
			unsigned char *pde = (unsigned char*)malloc(rsa_len + 1);
			memset(pde, 0, rsa_len + 1);
			int ret = RSA_public_decrypt(from.size(), 
										 (unsigned char*)from.data(), 
										 pde, 
										 rsa, 
										 padding);
			if(ret < 0){
				return false;
			}
			to.append((char*)pde, ret);
			return true;
		}

		static int base64_decode(std::string& from, std::string& to)
		{
			int len = 0;
			BIO *b64, *bmem;
			char* data = (char*)malloc(from.size() * 4);
			memset(data, 0, from.size() * 4);
			b64 = BIO_new(BIO_f_base64());
			BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
			bmem = BIO_new_mem_buf((void*)from.data(), from.size());
			bmem = BIO_push(b64, bmem);
			len = BIO_read(bmem, (void*)data, from.size());
			data[len] = 0;
			to.append((char*)data, len);
			free(data);
			BIO_free_all(bmem);
			return 0;
		}

		static void base64_encode(const std::string& from, std::string& to)
		{
			BIO *b64 = BIO_new(BIO_f_base64());
			BIO *bmem = BIO_new(BIO_s_mem());

			BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
			b64 = BIO_push(b64, bmem);
			BIO_write(b64, from.c_str(), from.size());
			BIO_flush(b64);

			BUF_MEM * bptr = NULL;
			BIO_get_mem_ptr(b64, &bptr);
			char * buff = (char *)malloc(bptr->length + 1);
			memcpy(buff, bptr->data, bptr->length);
			buff[bptr->length] = 0;
			to.append((char*)buff, bptr->length);
			free(buff);

			BIO_free_all(b64);
		}

#ifndef WIN32
		static std::string sha1_encode(std::string& from, const std::string& key)
		{
#if 0
			unsigned char md[SHA_DIGEST_LENGTH];  
			SHA_CTX c;  
			SHA1_Init(&c);  
			SHA1_Update(&c, from.data(), from.size());  
			SHA1_Final(md, &c);  
			OPENSSL_cleanse(&c, sizeof(c));  
#endif
			const EVP_MD *pEvpMd = EVP_sha1();   
			HMAC_CTX *pSignMacCtx = new HMAC_CTX;    
			HMAC_CTX_init(pSignMacCtx);      
			HMAC_Init(pSignMacCtx, key.data(), key.size(), pEvpMd);    
			HMAC_Update(pSignMacCtx, (unsigned char*)from.data(), from.size());    
			unsigned char pbMac[100] = {'\0'};     
			unsigned int uiMac = sizeof(pbMac);    
			HMAC_Final(pSignMacCtx, pbMac, &uiMac);      
			HMAC_CTX_cleanup(pSignMacCtx);    
			delete pSignMacCtx;  

			char buf[100] = {0};
			for(int i = 0; i < 100; i++ ){
				if(pbMac[i] == 0){
					break;
				}
				char tmp[3] = {0};
				sprintf(tmp, "%02X", pbMac[i]);
				strcat(buf, tmp);
			}  
			for(int i = 0; i < 100; i++){
				if(buf[i] >= 'A' && buf[i] <= 'Z'){
					buf[i] += 32;
				}
			}

			return std::string((char*)buf);
		}
#endif
	}
}

#endif
